﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace NavmeshPathfinding
{
    public class Camera : GameComponent
    {
        Core core;

        Vector3 position;
        Vector3 lookat;
        Vector3 viewup;
        Vector3 offset;
        Matrix projection;
        Matrix view;
        float angleGamma; //angle respect to xy-plane
        float angleTheta; //angle respect to x-axis
        float viewDistance;

        bool following;
        Agent target;

        public Camera(Core core) : base(core)
        {
            this.core = core;

            angleGamma = Config.Camera.DEFAULT_VIEW_ANGLE_GAMMA;
            angleTheta = Config.Camera.DEFAULT_VIEW_ANGLE_THETA;
            viewDistance = Config.Camera.DEFAULT_VIEW_DIST;

            updateCameraPosition();

            lookat = Vector3.Zero;
            offset = Vector3.Zero;
            viewup = Vector3.Up;

            view = Matrix.CreateLookAt(position, lookat, viewup);

            following = false;
        }

        public override void Initialize()
        {
            projection = Matrix.CreatePerspectiveFieldOfView(Config.Camera.FOV_ANGLE, core.GraphicsDevice.Viewport.AspectRatio, Config.Camera.FOV_NP, Config.Camera.FOV_FP);
            follow(core.agentList[0]);
            base.Initialize();
        }

        public override void Update(GameTime gameTime)
        {
            if (following)
            {
                offset = target.getPosition();
            }
            calculateViewMatrix();

            base.Update(gameTime);
        }

        private void updateCameraPosition()
        {
            position = new Vector3(viewDistance * Mathf.cos(angleGamma) * Mathf.cos(angleTheta),
                viewDistance * Mathf.sin(angleGamma),
                viewDistance * Mathf.cos(angleGamma) * Mathf.sin(angleTheta));
        }

        public void changeCameraAngle(int up, int right, int zoom)
        {
            angleGamma += Config.Camera.ANGLE_SENSITIVITY * up;
            angleTheta += Config.Camera.ANGLE_SENSITIVITY * right;
            viewDistance += Config.Camera.ZOOM_SENSITIVITY * zoom;

            angleGamma = Mathf.max(Mathf.min(angleGamma, Config.Camera.ANGLE_GAMMA_MAX), Config.Camera.ANGLE_GAMMA_MIN);
            angleTheta = Mathf.max(Mathf.min(angleTheta, Config.Camera.ANGLE_THETA_MAX), Config.Camera.ANGLE_THETA_MIN);
            viewDistance = Mathf.max(Mathf.min(viewDistance, Config.Camera.VIEW_DISTANCE_MAX), Config.Camera.VIEW_DISTANCE_MIN);

            updateCameraPosition();
            calculateViewMatrix();
        }

        public void changeCameraOffset(int up, int right)
        {
            offset.X += - Config.Camera.PAN_SENSITIVITY * Mathf.cos(angleTheta - Mathf.PI / 2) * right * viewDistance;
            offset.Y += Config.Camera.PAN_SENSITIVITY * up * viewDistance;
            offset.Z += - Config.Camera.PAN_SENSITIVITY * Mathf.sin(angleTheta - Mathf.PI / 2) * right * viewDistance;

            updateCameraPosition();

            calculateViewMatrix();
        }

        private void calculateViewMatrix()
        {
            view = Matrix.CreateLookAt(Vector3.Add(position, offset), Vector3.Add(lookat, offset), viewup);
        }

        public Vector3 getCameraPosition()
        {
            return position;
        }

        public Matrix getViewMatrix()
        {
            return view;
        }

        public Matrix getProjectionMatrix()
        {
            return projection;
        }

        public void follow(Agent agent)
        {
            following = true;
            target = agent;
        }

        public void unfollow()
        {
            following = false;
        }
    }
}
